#ifndef __M_ICSEARCH_H__
#define __M_ICSEARCH_H__
#include "logger.hpp"
#include <elasticlient/client.h>
#include<jsoncpp/json/json.h>
#include <cpr/cpr.h>  //Cpr是一个简单好用Http访问开源库
#include <iostream>
#include <memory>
#include <vector>
namespace xchat
{
bool Serialize(const Json::Value &val, std::string &dst)
{
    //先定义Json::StreamWriter 工厂类 Json::StreamWriterBuilder
    Json::StreamWriterBuilder swb;
    swb.settings_["emitUTF8"] = true;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    //通过Json::StreamWriter中的write接口进行序列化
    std::stringstream ss;
    int ret = sw->write(val, &ss);
    if(ret != 0)
    {
        LOG_INFO("Json序列化失败");
        return false;
    }
    dst = ss.str();
    return true;
}

bool UnSerialize(const std::string &src, Json::Value &val)
{
    Json::CharReaderBuilder crb;
    crb.settings_["emitUTF8"] = true;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    std::string err;
    bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
    if(ret == false)
    {
        LOG_INFO("Json反序列化失败 : {}");
        return false;
    }
    return true;
}

//新增字段
class ESIndex
{
public:
    ESIndex(std::shared_ptr<elasticlient::Client> &client, const std::string &name,
            const std::string &type = "_doc")
        :_name(name)
        ,_type(type)
        ,_client(client)
    {
        Json::Value analysis;
        Json::Value analyzer;
        Json::Value ik;
        Json::Value tokenizer;
        tokenizer["tokenizer"] = "ik_max_word";     //从上往下 从里到外 最里面是字符串 其余全是对象 ik_max_word(中文分词器)
        ik["ik"] = tokenizer;
        analyzer["analyzer"] = ik;
        analysis["analysis"] = analyzer;
        _index["settings"] = analysis;

    }
    //添加字段 就是添加属性name phone address等
    //keyword 字段的值会作为完整词条进入索引 默认索引 用于精确匹配 / 过滤的字段（keyword 类型）
    //text 字段的值会被分词器分词，然后每个词作为词条进入索引 默认索引
    //text + false 不参与索引，仅作为 “存储字段” 仅用于 “展示” 而非 “检索”。
    //analyzer 分词器类型   元信息不用分词
    //enabled 是否要构造索引
    ESIndex& append(const std::string &key, const std::string &type = "text",
                 const std::string &analyzer = "ik_max_word", bool enabled = true)//字段名称 字段类型  分词器类型 是否要构造索引
    {
        Json::Value fields;     //字段

        fields["type"] = type;
        fields["analyzer"] = analyzer;
        if(enabled == false) fields["enabled"] = enabled;; //默认值true
        //fields 是 properties的子字段
        _properties[key] = fields;

        return *this;
    }
    //创建 _properties时mapping的一部分
    bool create(const std::string &index_id = "default_index_id")
    {
        Json::Value mappings; 
        mappings["dynamic"] = true;
        mappings["properties"] = _properties;
        _index["mappings"] = mappings;

        std::string body;
        bool ret = Serialize(_index, body);
        if(ret == false)
        {
            LOG_ERROR("索引序列化失败！");
            return false;
        }
        //LOG_DEBUG("createIndex正文 {}", body);
        //发起新增数据请求
        try
        {
            auto rep = _client->index(_name, _type, index_id, body);
            // 3.打印响应状态码和响应正文
            if(rep.status_code < 200 || rep.status_code >= 300)
            {
                LOG_ERROR("创建ES索引{} 失败, 响应状态码异常: {}", _name, rep.status_code);
                return false;
            }
            //std::cout << rep.text << std::endl;
        }
        catch (std::exception &e)
        {
            LOG_ERROR("创建ES索引{} 失败: {}", _name, e.what());
            return false;
        }
        LOG_DEBUG("创建ES索引{} 成功", _name);
        return true;
    }
private:
    std::string _name;  //索引名称
    std::string _type;  //索引类型 
    Json::Value _properties; 
    Json::Value _index; 
    std::shared_ptr<elasticlient::Client> _client;
};

//新增具体对象
class ESInsert
{
public:
    ESInsert(std::shared_ptr<elasticlient::Client> &client, const std::string &name, 
            const std::string &type = "_doc")
        :_name(name)
        ,_type(type)
        ,_client(client)
    {

    }
    
    template<typename T>
    ESInsert& append(const std::string &key, const T &val)
    {
        _item[key] = val;
        return *this;
    }
    bool insert(const std::string id = "")
    {
        std::string body;
        bool ret = Serialize(_item, body);
        if(ret == false)
        {
            LOG_ERROR("索引序列化失败！");
            return false;
        }
        //LOG_DEBUG("append正文 {}", body);
        //发起新增数据请求
        try
        {
            auto rep = _client->index(_name, _type, id, body);
            if(rep.status_code < 200 || rep.status_code >= 300)
            {
                LOG_ERROR("新增ES数据{} 失败, 响应状态码异常: {}", body, rep.status_code);
                return false;
            }
            //std::cout << rep.text << std::endl;
        }
        catch (std::exception &e)
        {
            LOG_ERROR("新增ES数据{} 失败: {}", body, e.what());
            return false;
        }
        LOG_DEBUG("新增ES数据{} 成功", id);
        return true;
    }
private:
    std::string _name;  //索引名称
    std::string _type;  //索引类型 
    Json::Value _properties;
    Json::Value _item; 
    std::shared_ptr<elasticlient::Client> _client;
};

class ESRemove
{
public:
    ESRemove(std::shared_ptr<elasticlient::Client> &client, const std::string &name, 
            const std::string &type = "_doc")
        :_name(name)
        ,_type(type)
        ,_client(client)
    {}
    bool remove(const std::string &id)
    {
        try
        {
            auto rep = _client->remove(_name, _type, id);
            // 3.打印响应状态码和响应正文
            if (rep.status_code < 200 || rep.status_code >= 300)
            {
                LOG_ERROR("删除ES数据{} 失败, 响应状态码异常: {}", _name, rep.status_code);
                return false;
            }
            // std::cout << rep.text << std::endl;
        }
        catch (std::exception &e)
        {
            LOG_ERROR("删除ES数据{} 失败: {}", _name, e.what());
            return false;
        }
        LOG_DEBUG("删除ES数据成功 id: {}", id);
        return true;
    }

private:
    std::string _name;  //索引名称
    std::string _type;  //索引类型 
    std::shared_ptr<elasticlient::Client> _client;
};

class ESSearch
{
public:
    ESSearch(std::shared_ptr<elasticlient::Client> &client, const std::string &name,
             const std::string &type = "_doc")
        :_name(name)
        ,_type(type)
        ,_client(client)
    {}
    //terms是精确匹配 match是先分词找关键字匹配
    //追加一个必须不遵循的条件
    ESSearch& append_must_not_terms(const std::string &key, const std::vector<std::string> &vals)
    {
        Json::Value fields;
        for(const auto& val : vals)
        {
            fields[key].append(val);
        }
        Json::Value terms;
        terms["terms"] = fields;
        _must_not.append(terms);//数组append 不要覆盖
        return *this;
    }
    //追加一个必须遵循的条件
    //append_should_match	should（或）	match（全文）	text 类型字段的可选全文搜索条件	满足该条件或其他 should 条件即可被返回
    //append_must_term	must（与）	term（精确）	keyword / 数值类型字段的必选精确匹配条件	必须满足该精确条件，否则排除    
    //append_must_match	must（与）	match（全文）	text 类型字段的必选全文搜索条件	必须满足该全文匹配条件，否则排除 分词有一部分就行
    ESSearch& append_should_match(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value match;
        match["match"] = field;
        _should.append(match);
        return *this;
    }
    //必须遵循
    ESSearch& append_must_term(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value term;
        term["term"] = field;
        _must.append(term);
        return *this;
    }
    ESSearch& append_must_match(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value match;
        match["match"] = field;
        _must.append(match);
        return *this;
    }
    Json::Value search()
    {
        Json::Value cond;
        if(_must_not.empty() == false) cond["must_not"] = _must_not;
        if(_should.empty() == false) cond["should"] = _should;
        if(_must.empty() == false) cond["must"] = _must;
        Json::Value query;
        query["bool"] = cond;
        Json::Value root;
        root["query"] = query;

        std::string body;
        bool ret = Serialize(root, body);
        if(ret == false)
        {
            LOG_ERROR("索引序列化失败！");
            return false;
        }
        //LOG_DEBUG("搜索正文 {}", body);
        //发起新增数据请求
        cpr::Response rep;
        try
        {
            rep = _client->search(_name, _type, body);
            if(rep.status_code < 200 || rep.status_code >= 300)
            {
                LOG_ERROR("查询ES数据{} 失败, 响应状态码异常: {}", body, rep.status_code);
                return Json::Value();
            }
            //std::cout << rep.text << std::endl;
        }
        catch (std::exception &e)
        {
            LOG_ERROR("查询ES数据{} 失败: {}", body, e.what());
            return Json::Value();
        }
        //std::cout << rep.text << std::endl;
        LOG_DEBUG("查询ES数据成功");
        //3.需要对响应正文进行反序列化
        //LOG_INFO("响应正文 {}", rep.text);
        //std::cout << rep.text << std::endl;
        Json::Value json_res;
        ret = UnSerialize(rep.text, json_res);
        if(ret == false)
        {
            LOG_ERROR("查询ES数据失败, 结果反序列化失败: {}", rep.text);
        }
        return json_res["hits"]["hits"];
    }
private:
    std::string _name;  //索引名称
    std::string _type;  //索引类型
    Json::Value _must_not; //必须不遵循的条件
    Json::Value _should;    //必须遵循的条件
    Json::Value _must;
    std::shared_ptr<elasticlient::Client> _client;
};
}
#endif